#include "chatservice.hpp"
#include "public.hpp"
#include <muduo/base/Logging.h>

using namespace muduo;
using namespace std;

ChatService::ChatService()
{
    msgHandlerMap_.insert({LOGIN_MSG, std::bind(&ChatService::login, 
        this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)});
    msgHandlerMap_.insert({REG_MSG, std::bind(&ChatService::reg, 
        this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)});
    msgHandlerMap_.insert({ONE_CHAT_MSG, std::bind(&ChatService::oneChat, this, 
        placeholders::_1, placeholders::_2, placeholders::_3)});
    msgHandlerMap_.insert({ADD_FRIEND_MSG, std::bind(&ChatService::addFriend, this, 
        placeholders::_1, placeholders::_2, placeholders::_3)});
    msgHandlerMap_.insert({CREATE_GROUP_MSG, std::bind(&ChatService::createGroup, this, 
        placeholders::_1, placeholders::_2, placeholders::_3)});
    msgHandlerMap_.insert({ADD_GROUP_MSG, std::bind(&ChatService::addGroup, this, 
        placeholders::_1, placeholders::_2, placeholders::_3)});
    msgHandlerMap_.insert({GROUP_CHAT_MSG, std::bind(&ChatService::groupChat, this, 
        placeholders::_1, placeholders::_2, placeholders::_3)});
}

ChatService* ChatService::instance()
{
    static ChatService service;
    return &service;
}

void ChatService::login(const muduo::net::TcpConnectionPtr &conn, 
    json &js, muduo::Timestamp time)
{
    LOG_INFO << "do login service!!!";
    int id = js["id"];
    string pwd = js["password"];
    json response;
    User user = userModel_.query(id);

    if (user.getId() == id && user.getPwd() == pwd) {
        if (user.getState() == "online") {
            response["msgid"] = LOGIN_MSG_ACK;
            response["errno"] = 2;
            response["errmsg"] = "user was online";
        } else {
            {
                lock_guard<mutex> lock(connMutex_);
                userConnMap_.insert({id, conn});
            }
            user.setState("online");
            userModel_.updateState(user);

            response["msgid"] = LOGIN_MSG_ACK;
            response["errno"] = 0;
            response["id"] = user.getId();       
            response["name"] = user.getName();
            vector<string> messages = offlineMsgModel_.query(id);
            if (!messages.empty()) {
                response["offlinemsg"] = messages;
                offlineMsgModel_.remove(id);
            }
            vector<User> userList = friendModel_.query(id);
            if (!userList.empty()) {
                vector<string> userListString;
                for (User &user: userList) {
                    json jsUser;
                    jsUser["id"] = user.getId();
                    jsUser["name"] = user.getName();
                    jsUser["state"] = user.getState();
                    userListString.push_back(jsUser.dump()); 
                }
                response["friends"] = userListString;
            }

        }
    } else {
        response["msgid"] = LOGIN_MSG_ACK;
        response["errno"] = 2;
        response["errmsg"] = "user not exist or password error";    
    }
    conn->send(response.dump());
}

void ChatService::reg(const muduo::net::TcpConnectionPtr &conn, 
json &js, muduo::Timestamp time)
{
    LOG_INFO << "do reg service!!!";
    User user;
    std::string name = js["name"];
    std::string pwd = js["password"];
    user.setName(name);
    user.setPwd(pwd);
    bool state = userModel_.insert(user);
    json response;
    if (state) {
        response["msgid"] = REG_MSG_ACK;
        response["errno"] = 0;
        response["id"] = user.getId();
    } else {
        response["msgid"] = REG_MSG_ACK;
        response["erron"] = 1;      
    }
    conn->send(response.dump());
}

MsgHandler ChatService::getHangler(int msgid)
{
    auto it = msgHandlerMap_.find(msgid);

    if (it == msgHandlerMap_.end()) {
        return [=](const muduo::net::TcpConnectionPtr &conn, 
                json &js, muduo::Timestamp tim) {
            LOG_ERROR << "msgid:" << msgid << " can not find handler!";
        };
    } else {
        return msgHandlerMap_[msgid];
    }
}

void ChatService::clientCloseExecption(const muduo::net::TcpConnectionPtr &conn)
{
    User user;

    {   
        lock_guard<mutex> lock(connMutex_);  
        for (auto it = userConnMap_.begin(); it != userConnMap_.end(); ++it) {
            if (it->second == conn) {
                user.setId(it->first);
                userConnMap_.erase(it);
                break;
            }
        }
    }
    if (user.getId() != -1) {
        user.setState("offline");
        userModel_.updateState(user);
    }
}

void ChatService::oneChat(const muduo::net::TcpConnectionPtr&, json &js, muduo::Timestamp time)
{
    int toId = js["to"].get<int>();
    bool userState;

    do {
        lock_guard<mutex> lock(connMutex_); 
        auto it = userConnMap_.find(toId);
        userState = false;
        if (it == userConnMap_.end()) {
            break;
        } 
        userState = true;
        it->second->send(js.dump());
    } while (0);

    if (!userState) {
        offlineMsgModel_.insert(toId, js.dump());
    }

}

void ChatService::addFriend(const muduo::net::TcpConnectionPtr&, json &js, muduo::Timestamp time)
{
    int userid = js["id"].get<int>();
    int friendid = js["friendid"].get<int>();

    friendModel_.insert(userid, friendid);
}

void ChatService::createGroup(const muduo::net::TcpConnectionPtr&, json &js, muduo::Timestamp time)
{
    int userid = js["id"].get<int>();
    string name = js["groupname"];
    string desc = js["groupdesc"];
    Group group(-1, name, desc);
    if (groupModel_.createGroup(group)) {
        groupModel_.addGroup(userid, group.getId(), "creator");
    }
}

void ChatService::addGroup(const muduo::net::TcpConnectionPtr&, json &js, muduo::Timestamp time)
{
    int userid = js["id"].get<int>();
    int groupid = js["groupid"].get<int>();
    groupModel_.addGroup(userid, groupid, "normal");
}

void ChatService::groupChat(const muduo::net::TcpConnectionPtr&, json &js, muduo::Timestamp time)
{
    int userid = js["id"].get<int>();
    int groupid = js["groupid"].get<int>();
    vector<int> userList = groupModel_.queryGroupUsers(userid, groupid);
    lock_guard<mutex> lock(connMutex_);
    for (int id : userList) {
        auto it = userConnMap_.find(id);
        if (it != userConnMap_.end()) {
            it->second->send(js.dump());
        } else {
            offlineMsgModel_.insert(id, js.dump());
        }
        
    }
}

void ChatService::reset()
{
    userModel_.resetState();
}